#include "if.h"

#include <string.h>

#include <unistd.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <net/ethernet.h>

#include "scope_guard.h"
#include "exception.h"
#include "log.h"


namespace evm {

If::If(string name) : name(name) {
    try {
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        check_unix_error(fd, "socket");
        auto fd_guard = make_guard([fd] { close(fd); });

        struct ifreq ifr;

        strncpy(ifr.ifr_name, name.c_str(), IFNAMSIZ - 1);

        ifr.ifr_addr.sa_family = AF_INET;

        auto ntop = [](string& result, struct sockaddr& addr) {
            char r[INET_ADDRSTRLEN];
            auto sa = (struct sockaddr_in*)&addr;
            auto ret = inet_ntop(AF_INET, (const void*)&sa->sin_addr, r, INET_ADDRSTRLEN);
            if (!ret)
                throw_system_error("inet_ntop");
            result = r;
        };

        check_unix_error(ioctl(fd, SIOCGIFADDR, &ifr), "ioctl");
        ntop(addr, ifr.ifr_addr);

        check_unix_error(ioctl(fd, SIOCGIFBRDADDR, &ifr), "ioctl");
        ntop(broad_addr, ifr.ifr_broadaddr);

        check_unix_error(ioctl(fd, SIOCGIFNETMASK, &ifr), "ioctl");
        ntop(netmask, ifr.ifr_netmask);

        check_unix_error(ioctl(fd, SIOCGIFHWADDR, &ifr), "ioctl");
        auto ea = (struct ether_addr*)ifr.ifr_hwaddr.sa_data;
        auto eao = ea->ether_addr_octet;
        mac = fmt::format("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", eao[0], eao[1], eao[2], eao[3], eao[4], eao[5]);

        check_unix_error(ioctl(fd, SIOCGIFFLAGS, &ifr), "ioctl");
        flags = ifr.ifr_flags;

        check_unix_error(ioctl(fd, SIOCGIFMTU, &ifr), "ioctl");
        mtu = ifr.ifr_mtu;

        check_unix_error(ioctl(fd, SIOCGIFMETRIC, &ifr), "ioctl");
        metric = ifr.ifr_metric;

        check_unix_error(ioctl(fd, SIOCGIFINDEX, &ifr), "ioctl");
        index = ifr.ifr_ifindex;
    } catch (const system_error& e) {
        log_error(e.what());
        return;  // failed
    }

    complete_ = true;  // succeeded
}

#define FLAG_TO_STR(flags, flag, str) \
if (flags & flag) { \
    str += #flag; \
    str += '|'; \
}

void If::log() {
    if (!complete_)
        return;
    
    string flags_str;
    FLAG_TO_STR(flags, IFF_UP, flags_str);
    FLAG_TO_STR(flags, IFF_BROADCAST, flags_str);
    FLAG_TO_STR(flags, IFF_LOOPBACK, flags_str);
    FLAG_TO_STR(flags, IFF_POINTOPOINT, flags_str);
    FLAG_TO_STR(flags, IFF_NOTRAILERS, flags_str);
    FLAG_TO_STR(flags, IFF_RUNNING, flags_str);
    FLAG_TO_STR(flags, IFF_NOARP, flags_str);
    FLAG_TO_STR(flags, IFF_PROMISC, flags_str);
    FLAG_TO_STR(flags, IFF_MULTICAST, flags_str);
    if (!flags_str.empty())
        flags_str.back() = ' ';

    log_info("\nInterface: {}\nAddress: {}\nBroadcast address: {}\nMAC address: {}\nNetmask: {}\n"
             "Flags: {}\nMTU: {}\nMetric: {}\nIndex: {}\n",
             name, addr, broad_addr, mac, netmask, flags_str, mtu, metric, index);
}

}
